﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.Data;
using System.Collections;
using SmartProject.Data;
using SmartProject.Exceptions;

namespace SmartProject
{
    /// <summary>
    /// The Entity class is used to support a variety of data-related functions.
    /// </summary>
    public abstract class Entity
    {
        private string _lastError;
        private readonly IDataProvider _dataProvider = DataProvider.Instance;
        private readonly List<DataParameter> _dataParameters = new List<DataParameter>();
        private readonly List<BusinessRule> _businessRules = new List<BusinessRule>();
        private readonly List<BusinessRulePreference> _preferences = new List<BusinessRulePreference>();
        private readonly List<BusinessRuleParameter> _businessRuleParameters = new List<BusinessRuleParameter>();

        /// <summary>
        /// Initializes the Entity class with the data parameters that are
        /// implemented.
        /// </summary>
        /// <param name="dataParameters"></param>
        protected void Initialize(DataParameter[] dataParameters)
        {
            _dataParameters.AddRange(dataParameters);
        }

        /// <summary>
        /// Adds a required business rule parameter to be evaluated.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public void AddBusinessRuleParameter(string name, object value)
        {
            AddBusinessRuleParameter(new BusinessRuleParameter(name, value));
        }

        /// <summary>
        /// Adds a required business rule parameter to be evaluated.
        /// </summary>
        /// <param name="businessRuleParameter">Business rule parameter.</param>
        public void AddBusinessRuleParameter(BusinessRuleParameter businessRuleParameter)
        {
            _businessRuleParameters.Add(businessRuleParameter);
        }

        /// <summary>
        /// Gets a business rule parameter value. This is used only in the executation of
        /// the business rule.
        /// </summary>
        /// <param name="name">Name of the business rule parameter value.</param>
        /// <returns>Object value instance.</returns>
        public object GetBusinessRuleParameterValue(string name)
        {
            // Make the search case-insensitive.
            string search = name.ToLower();
            foreach (BusinessRuleParameter businessRuleParameter in _businessRuleParameters)
            {
                if (businessRuleParameter.Name.ToLower() == search)
                {
                    return businessRuleParameter.Value;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the DataParameter with the specified name.
        /// </summary>
        /// <param name="name">Name of the DataParameter instance.</param>
        /// <returns>DataParameter.</returns>
        public DataParameter GetDataParameter(string name, bool isStoredProc)
        {
            foreach (DataParameter dataParameter in _dataParameters)
            {
                if (dataParameter.GetName(isStoredProc).ToLower() == name.ToLower())
                {
                    return dataParameter;
                }
            }
            return null;
        }

        /// <summary>
        /// Sets the attribute value. The value is a required field and MUST be populated.
        /// </summary>
        /// <param name="name">Name of the attribute.</param>
        /// <param name="value">Value of the attribute.</param>
        public void SetAttribute(string name, object value)
        {
            SetAttribute(name, value, true);
        }

        /// <summary>
        /// Sets the attribute value.
        /// </summary>
        /// <param name="name">Name of the attribute.</param>
        /// <param name="value">Value of the attribute.</param>
        /// <param name="isRequired">Check to see if the object is null.</param>
        /// <remarks>
        /// Throw the RequiredDataParameter exception if this is required and no
        /// valid value is entered.
        /// </remarks>
        public void SetAttribute(string name, object value, bool isRequired)
        {
            bool isReturnDBNull = false;

            if (isRequired && value == null)
            {
                throw new Exceptions.RequiredDataParameter(
                    String.Format("{0} cannot be empty.", name), name);
            }

            DataParameter dataParameter = GetDataParameter(name, false);
            switch (dataParameter.Type)
            {
                case DataType.DateTime:
                    if (value.GetType() == typeof(String))
                    {
                        string test = (string)value;
                        if (String.IsNullOrEmpty(test))
                        {
                            if (isRequired)
                            {
                                FireRequiredParameterException(name);
                            }
                            else
                            {
                                isReturnDBNull = true;
                            }
                        }
                    }
                    else if (value.GetType() == typeof(DateTime))
                    {
                        if ((DateTime)value == DateTime.MinValue)
                        {
                            if (isRequired)
                            {
                                FireRequiredParameterException(name);
                            }
                            else
                            {
                                isReturnDBNull = true;
                            }
                        }
                    }
                    else
                    {
                        throw new InvalidCastException(String.Format(
                            "Please enter a valid value for {0}.", name));
                    }
                    break;

                case DataType.Integer:
                    if (value.GetType() == typeof(String))
                    {
                        string test = (string)value;
                        if (String.IsNullOrEmpty(test) || test == "0")
                        {
                            if (isRequired)
                            {
                                FireRequiredParameterException(name);
                            }
                            else
                            {
                                isReturnDBNull = true;
                            }
                        }
                    }
                    else if (value.GetType() == typeof(Int32))
                    {
                        if ((int)value == 0)
                        {
                            if (isRequired)
                            {
                                FireRequiredParameterException(name);
                            }
                            else
                            {
                                isReturnDBNull = true;
                            }
                        }
                    }
                    else
                    {
                        throw new InvalidCastException(String.Format(
                            "Please enter a valid value for {0}.", name));
                    }
                    break;

                case DataType.String:
                    if ((string)value == String.Empty)
                    {
                        if (isRequired)
                        {
                            FireRequiredParameterException(name);
                        }
                        else
                        {
                            isReturnDBNull = true;
                        }
                    }
                    break;

                case DataType.Bytes:
                    if (value == null)
                    {
                        if (isRequired)
                        {
                            FireRequiredParameterException(name);
                        }
                        else
                        {
                            isReturnDBNull = true;
                        }
                    }
                    break;
            }

            if (isReturnDBNull)
            {
                dataParameter.SetValueAsDBNull();
            }
            else
            {
                dataParameter.Value = value;
            }
        }

        private void FireRequiredParameterException(string name)
        {
            throw new Exceptions.RequiredDataParameter(
                String.Format("{0} is a required field.", name), name);
        }

        public List<BusinessRule> BusinessRules
        {
            get { return _businessRules; }
        }

        /// <summary>
        /// Gets or sets the preferences for this data parameter.
        /// </summary>
        public List<BusinessRulePreference> Preferences
        {
            get { return _preferences; }
        }

        /// <summary>
        /// Sets up the DataHelper to get ready to execute the stored proc.
        /// </summary>
        private void Setup(bool isRun)
        {
            _dataProvider.Initialize();
            if (isRun)
            {
                foreach (BusinessRulePreference preference in _preferences)
                {
                    RunBusinessRule(preference.GetSelectedBusinessRule());
                }

                foreach (BusinessRule businessRule in _businessRules)
                {
                    RunBusinessRule(businessRule);
                }
            }

            foreach (Data.DataParameter dataParameter in _dataParameters)
            {
                if (dataParameter.IsDirty)
                {
                    _dataProvider.Add(dataParameter);
                }
            }

            Reset();
        }

        /// <summary>
        /// Loads the stored procedure that returns a single string value.
        /// </summary>
        /// <param name="storedProcedure">Stored procedure name.</param>
        /// <returns>String value.</returns>
        public static string LoadStringValue(IDataProvider dataProvider, string storedProcedure)
        {
            string value = String.Empty;
            Hashtable table = dataProvider.Load(storedProcedure);
            foreach (string key in table.Keys)
            {
                value = table[key].ToString();
                break;
            }
            return value;
        }

        public static int GetCounts(IDataProvider dataProvider, string storedProcedure)
        {
            int counts = -1;
            Hashtable table = dataProvider.Load(storedProcedure);
            foreach (string key in table.Keys)
            {
                counts = Convert.ToInt32(table[key]);
                break;
            }
            return counts;
        }

        private void RunBusinessRule(BusinessRule businessRule)
        {
            if (businessRule != null)
            {
                // Implement the business rule.
                businessRule.Implement(this);
            }
        }

        protected DataTable Find(string storedProc)
        {
            Setup(false);
            return _dataProvider.Find(storedProc);
        }

        protected bool Load(string storedProc)
        {
            Setup(false);
            Hashtable values = _dataProvider.Load(storedProc);
            if (values.Count == 0)
            {
                Logging.Logger.Log(this.GetType().ToString(), "Load",
                    String.Format("The stored proc yields no return entity values: {0}.", storedProc), "Debug");
                return false;
            }
            else
            {
                foreach (string key in values.Keys)
                {
                    object obj = values[key];
                    if (obj != DBNull.Value)
                    {
                        this.SetAttribute(key, values[key], false);
                    }
                }
                return true;
            }
        }

        public Object GetLastAttributeValue(string name)
        {
            DataParameter dataParameter = GetDataParameter(name, false);
            return dataParameter.LastValue;
        }

        public Object GetAttributeValue(string name)
        {
            DataParameter dataParameter = GetDataParameter(name, false);
            object value = dataParameter.Value;
            if (value == DBNull.Value)
            {
                return null;
            }
            else
            {
                return value;
            }
        }

        public DateTime GetAttributeDateTime(string name)
        {
            Object obj = GetAttributeValue(name);
            if (obj != null)
            {
                return Convert.ToDateTime(obj);
            }
            return DateTime.MinValue;
        }

        private string _storedProc;
        /// <summary>
        /// Allows only the business rule to access the stored procedure used.
        /// </summary>
        public string StoredProc
        {
            get { return _storedProc; }
        }

        /// <summary>
        /// Runs the stored procedure with the set attributes.
        /// </summary>
        /// <param name="storedProc">Stored procedure to run.</param>
        protected void Run(string storedProc)
        {
            _storedProc = storedProc;
            Setup(true);
            _dataProvider.Run(storedProc);
        }

        private void Reset()
        {
            foreach (DataParameter dataParameter in _dataParameters)
            {
                dataParameter.Reset();
            }
            _storedProc = String.Empty;
        }

        /// <summary>
        /// Gets the selected business rule.
        /// </summary>
        /// <param name="preferenceName"></param>
        /// <returns></returns>
        public BusinessRule GetSelectedBusinessRule(string preferenceName)
        {
            foreach (BusinessRulePreference preference in _preferences)
            {
                if (preference.Name.ToLower() == preferenceName.ToLower())
                {
                    return preference.GetSelectedBusinessRule();
                }
            }
            return null;
        }

        /// <summary>
        /// Gets a list of DataParameters.
        /// </summary>
        public List<DataParameter> DataParameters
        {
            get { return _dataParameters; }
        }

        /// <summary>
        /// Gets or sets the last error with any methods/properties.
        /// </summary>
        public string LastError
        {
            get { return _lastError; }
            set { _lastError = value; }
        }

        /// <summary>
        /// Implement the loading of attribute values for a single entity returned.
        /// </summary>
        public abstract void Load();

        /// <summary>
        /// Implement the saving of a new entity.
        /// </summary>
        public abstract void Save();

        /// <summary>
        /// Updates an existing entity values.
        /// </summary>
        public abstract void Update();

        /// <summary>
        /// Removes an existing entity.
        /// </summary>
        public abstract void Remove();

        /// <summary>
        /// Gets the return id from the stored procedure.
        /// </summary>
        public int ReturnId
        {
            get { return _dataProvider.ReturnId; }
        }
    }
}
